package cn.camio1945.asm2vhd4bochs;

import cn.camio1945.asm2vhd4bochs.constant.VhdFooterFieldConstant;
import cn.camio1945.asm2vhd4bochs.entity.VhdFooterField;
import cn.hutool.core.compiler.CompilerException;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.*;
import cn.hutool.log.Log;
import org.jetbrains.annotations.NotNull;
import picocli.CommandLine;

import java.nio.ByteOrder;
import java.util.*;
import java.util.stream.Collectors;

import static cn.camio1945.asm2vhd4bochs.constant.VhdFooterFieldConstant.ZERO_BASED_ORDER_TO_SIZE_MAP;
import static cn.camio1945.asm2vhd4bochs.constant.VhdFooterFieldConstant.ZeroBasedOrder.*;
import static cn.camio1945.util.StaticMethodUtil.*;
import static cn.hutool.core.text.CharSequenceUtil.format;
import static cn.hutool.core.text.CharSequenceUtil.isBlank;

/**
 * 项目入口。大部分的字段和方法的修饰符都是 protected，是为了方便做单元测试。
 * <p>
 * Project entry. Most of the modifiers of fields and methods are protected for easy unit testing.
 * <p>
 * @author Camio1945
 */
public class MainApplication {
  protected static final Log log = Log.get();

  /**
   * 64KB  : 64 * 1024
   * 经过反复测试 64kb 是最小值，否则 bochs 会报错，因为低于这个值之后，计算出的柱面数量为 0
   * <p>
   * 64KB  : 64 * 1024
   * After repeated tests, 64kb is the minimum value, otherwise bochs will report an error,
   * because after it is lower than this value, the calculated number of cylinders is 0
   */
  protected static int vhdFileSize = 64 * 1024;

  /**
   * 一个扇区有多少个字节
   * <p>
   * how many bytes per sector
   */
  protected static int bytesPerSector = 512;

  /**
   * 当前文件夹路径。
   * 如果是 Intellij Idea 开发环境，则是项目根目录，如：D:/git/asm2vhd4bochs。
   * 如果是执行 asm2vhd4bochs.exe 文件，则是 asm2vhd4bochs.exe 文件所在的文件夹路径，如：D:/git/asm2vhd4bochs/target。
   * <p>
   * Current folder path.
   * If it is Intellij Idea development environment, it is the project root directory, such as: D:/git/asm2vhd4bochs.
   * If it is executing the asm2vhd4bochs.exe file, it is the folder path where the asm2vhd4bochs.exe file is located, such as: D:/asm2vhd4bochs
   */
  protected static String currentFolderPath;

  /**
   * bochs 文件夹路径。如：D:/asm2vhd4bochs/Bochs。
   * <p>
   * Bochs folder path. Such as: D:/asm2vhd4bochs/Bochs
   */
  protected static String bochsFolderPath;

  /**
   * bochs.exe（运行模式）文件路径。如：D:/asm2vhd4bochs/Bochs/bochs.exe
   * <p>
   * bochs.exe (run mode) file path. Such as: D:/asm2vhd4bochs/Bochs/bochs.exe
   */
  protected static String bochsRunExeFilePath;

  /**
   * bochsdbg.exe（调试模式）文件路径。如：D:/asm2vhd4bochs/Bochs/bochsdbg.exe
   * <p>
   * bochsdbg.exe (debug mode) file path. Such as: D:/asm2vhd4bochs/Bochs/bochsdbg.exe
   */
  protected static String bochsDebugExeFilePath;

  /**
   * bochsrc.bxrc 配置文件路径。如：D:/asm2vhd4bochs/Bochs/bochsrc.bxrc
   * <p>
   * bochsrc.bxrc configuration file path. Such as: D:/asm2vhd4bochs/Bochs/bochsrc.bxrc
   */
  protected static String bochsConfigFilePath;

  /**
   * bochs配置文件模板（bochsrcTemplate.txt）文件路径。如：D:/asm2vhd4bochs/Bochs/bochsrcTemplate.txt
   * <p>
   * bochs configuration file template (bochsrcTemplate.txt) file path. Such as: D:/asm2vhd4bochs/Bochs/bochsrcTemplate.txt
   */
  protected static String bochsrcTemplateFilePath;

  /**
   * nasm.exe 文件路径。如：D:/asm2vhd4bochs/NASM/nasm.exe
   * <p>
   * nasm.exe file path. Such as: D:/asm2vhd4bochs/NASM/nasm.exe
   */
  protected static String nasmExeFilePath;

  /**
   * 汇编源码文件路径。如：D:/assembly/HelloWorld.asm
   * <p>
   * Assembly source code file path. Such as: D:/assembly/HelloWorld.asm
   */
  protected static String asmSourceCodeFilePath;

  /**
   * 生成的二进制文件路径。如：D:/asm2vhd4bochs/OutputBin/HelloWorld.bin
   * <p>
   * Generated binary file path. Such as: D:/asm2vhd4bochs/OutputBin/HelloWorld.bin
   */
  protected static String binFilePath;

  /**
   * 生成的 vhd 文件路径。如：D:/asm2vhd4bochs/OutputVhd/HelloWorld.vhd
   * <p>
   * Generated vhd file path. Such as: D:/asm2vhd4bochs/OutputVhd/HelloWorld.vhd
   */
  protected static String vhdFilePath;

  /**
   * bochs 生成的 vhd 文件的锁文件路径。如：D:/asm2vhd4bochs/OutputVhd/HelloWorld.vhd.lock
   * <p>
   * The lock file path of the vhd file generated by bochs. Such as: D:/asm2vhd4bochs/OutputVhd/HelloWorld.vhd.lock
   */
  protected static String vhdLockFilePath;

  /**
   * true 代表运行模式，false 代表调试模式
   * <p>
   * true means run mode, false means debug mode
   */
  protected static boolean isRun;

  /**
   * 命令行参数
   * <p>
   * Command line arguments
   */
  protected static Arguments arguments = new Arguments();

  public static void main(String[] args) {
    try {
      initArguments(args);
      if (arguments.help) {
        printHelp();
        return;
      }
      initStaticFields();
      asm2bin();
      killBochsProcess();
      bin2vhd();
      delLockFile();
      generateBochsConfigurationFile();
      runOrDebugBochs();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  protected static void initArguments(String[] args) {
    if (args == null || args.length == 0) {
      return;
    }
    new CommandLine(arguments).parseArgs(args);
  }

  protected static String printHelp() {
    initCurrentFolderPath();
    String msg = """
        Usage: asm2vhd4bochs.exe [options]

        Example: asm2vhd4bochs.exe -f="{}/NASM/HelloWorld.asm" -d

        Options:
          -h, --help  Print this help message and exit

          -f="<your asm source file absolute path>"
              note: if the path contains spaces, please use double quotes
              the default value is: {}/NASM/HelloWorld.asm
          -r
              run the Bochs (instead of debug)

          -d
              debug the Bochs (instead of run)

      """;
    log.info("\n\n" + msg, currentFolderPath, currentFolderPath);
    return msg;
  }

  protected static void generateBochsConfigurationFile() {
    log.info("generating bochs configuration file...");
    String template = FileUtil.readUtf8Lines(bochsrcTemplateFilePath)
                              .stream()
                              .collect(Collectors.joining("\n"));
    String displayLibrary = isRun ? "win32" : "win32, options=\"gui_debug\"";
    String folderPath = currentFolderPath.replace("/", "\\");
    String windowsVhdFilePath = vhdFilePath.replace("/", "\\");
    String configuration = format(template, displayLibrary, folderPath, folderPath, windowsVhdFilePath);
    FileUtil.writeUtf8String(configuration, bochsConfigFilePath);
  }

  protected static void runOrDebugBochs() {
    log.info(isRun ? "running bochs..." : "debugging bochs...");
    String bochsExeFilePath = isRun ? bochsRunExeFilePath : bochsDebugExeFilePath;
    RuntimeUtil.exec("cmd", "/c", "start",
      bochsExeFilePath, "-q", "-f", bochsConfigFilePath);
  }

  protected static void killBochsProcess() {
    log.info("killing bochs process...");
    RuntimeUtil.exec("taskkill /F /IM bochs.exe");
    RuntimeUtil.exec("taskkill /F /IM bochsdbg.exe");
    int maxTryTimes = 10;
    int triedTimes = 0;
    while (triedTimes < maxTryTimes && isBochsProcessExist()) {
      triedTimes++;
      ThreadUtil.sleep(300);
    }
  }

  protected static boolean isBochsProcessExist() {
    return RuntimeUtil.execForLines("tasklist")
                      .stream()
                      .anyMatch(
                        line -> line.contains("bochs")
                          && !line.contains("asm2vhd4bochs.exe")
                      );
  }

  protected static void initStaticFields() {
    log.info("Initializing static fields...");
    initCurrentFolderPath();
    initAsmSourceCodeFilePath();
    initIsRun();
    initPaths();
  }

  /**
   * 使用 nasm.exe 把 asm 源码文件编译成 bin 文件
   * <p>
   * use nasm.exe to compile asm source code to bin file
   */
  protected static void asm2bin() {
    log.info("compiling asm source code to bin file...");
    FileUtil.del(binFilePath);
    String cmd = format("\"{}\" -f bin \"{}\" -o \"{}\"",
      nasmExeFilePath, asmSourceCodeFilePath, binFilePath);
    String res = RuntimeUtil.execForStr(cmd);
    int maxTryTimes = 30;
    int triedTimes = 0;
    while (triedTimes < maxTryTimes && !FileUtil.exist(binFilePath)) {
      triedTimes++;
      ThreadUtil.sleep(100);
    }
    if (FileUtil.exist(binFilePath)) {
      log.info("compile asm source code to bin file successfully : " + binFilePath);
    } else {
      throw new CompilerException("compile asm source code to bin file failed : " + res);
    }
  }

  /**
   * 使用上一步编译成的 bin 文件作为起始数据，在起在起始扇区和结束扇区之间填充 0，
   * 自动生成 vhd 文件的 footer，
   * 把数据和 footer 合并成一个 vhd 文件。
   * <p>
   * Use the bin file compiled in the previous step as the initial data,
   * fill in 0 between the starting sector and the ending sector,
   * automatically generate the footer of the vhd file,
   * and merge the data and footer into a vhd file.
   */
  protected static void bin2vhd() {
    log.info("generating vhd file...");
    FileUtil.del(vhdFilePath);
    byte[] binFileBytes = FileUtil.readBytes(binFilePath);
    Assert.isTrue(binFileBytes.length <= bytesPerSector,
      "bin file size must be less than or equal to " + bytesPerSector + ", but actual is " + binFileBytes.length);
    List<Byte> byteList = Convert.convert(List.class, binFileBytes);
    fillDataBytes(byteList);
    byte[] footerBytes = generateVhdFooter();
    byteList.addAll(Convert.convert(List.class, footerBytes));
    byte[] vhdFileBytes = Convert.convert(byte[].class, byteList);
    Assert.isTrue(vhdFileBytes.length == vhdFileSize);
    FileUtil.writeBytes(vhdFileBytes, vhdFilePath);
    log.info("generate vhd file successfully : " + vhdFilePath);
  }

  /**
   * 删除 bochs 锁文件，否则 bochs 会报错
   * <p>
   * delete bochs lock file, otherwise bochs will report an error
   */
  protected static void delLockFile() {
    FileUtil.del(vhdLockFilePath);
  }

  protected static void initPaths() {
    bochsFolderPath = currentFolderPath + "/Bochs";
    bochsRunExeFilePath = bochsFolderPath + "/bochs.exe";
    bochsDebugExeFilePath = bochsFolderPath + "/bochsdbg.exe";
    bochsConfigFilePath = bochsFolderPath + "/bochsrc.bxrc";
    bochsrcTemplateFilePath = bochsFolderPath + "/bochsrcTemplate.txt";

    nasmExeFilePath = currentFolderPath + "/NASM/nasm.exe";

    // asm 文件的主文件名，如 HelloWorld.asm 的主文件名为 HelloWorld
    // main name of asm file, such as HelloWorld.asm's main name is HelloWorld
    String asmMainName = FileUtil.mainName(asmSourceCodeFilePath);

    binFilePath = format("{}/OutputBin/{}.bin", currentFolderPath, asmMainName);
    vhdFilePath = format("{}/OutputVhd/{}.vhd", currentFolderPath, asmMainName);
    vhdLockFilePath = vhdFilePath + ".lock";
  }

  protected static void initCurrentFolderPath() {
    currentFolderPath = getCurrentExecutingProjectFolderPath();
  }

  protected static void initAsmSourceCodeFilePath() {
    asmSourceCodeFilePath = arguments.asmSourceCodeFilePath;
    if (isBlank(asmSourceCodeFilePath)) {
      asmSourceCodeFilePath = currentFolderPath + "/NASM/HelloWorld.asm";
      log.info("Command line argument -f is not specified, " +
        "using default asm source code file path: " + asmSourceCodeFilePath);
    }
    Assert.isTrue(FileUtil.exist(asmSourceCodeFilePath), "file does not exist : {}", asmSourceCodeFilePath);
  }

  protected static void initIsRun() {
    Assert.isFalse(arguments.run && arguments.debug, "can not specify both -r and -d");
    if (!arguments.run && !arguments.debug) {
      log.info("Command line argument -r or -d is not specified, using default value : -r");
      arguments.run = true;
    }
    isRun = arguments.run;
  }

  /**
   * 比如说最终的 vhd 文件的总大小是64kb，头部占了 512 字节，尾部还要占 512 字节，中间的部分都要用 0 来填充。
   * <p>
   * For example, the total size of the final vhd file is 64kb,
   * the header occupies 512 bytes, the footer also occupies 512 bytes,
   * and the middle part is filled with 0.
   * @param byteList
   */
  protected static void fillDataBytes(@NotNull List<Byte> byteList) {
    if (byteList.size() == bytesPerSector) {
      Assert.isTrue(byteList.get(bytesPerSector - 2) == (byte) 0x55);
      Assert.isTrue(byteList.get(bytesPerSector - 1) == (byte) 0xAA);
    } else {
      Assert.isTrue(byteList.size() <= bytesPerSector - 2);
      // 补齐 byteList 的 510 个字节
      byteList.addAll(Collections.nCopies(bytesPerSector - 2 - byteList.size(), (byte) 0));
      byteList.add((byte) 0x55);
      byteList.add((byte) 0xAA);
    }
    // 2 * bytesPerSector 是因为 footer 也是一个扇区，加上起始扇区，一共是 2 个扇区。
    // 2 * bytesPerSector is because the footer is also a sector, plus the starting sector, a total of 2 sectors.
    byteList.addAll(Collections.nCopies(vhdFileSize - (2 * bytesPerSector), (byte) 0));
  }

  protected static byte[] generateVhdFooter() {
    VhdFooterField cookie = getCookie();
    VhdFooterField features = getFeatures();
    VhdFooterField fileFormatVersion = getFileFormatVersion();
    VhdFooterField dataOffset = getDataOffset();
    VhdFooterField timeStamp = getTimeStamp();
    VhdFooterField creatorApplication = getCreatorApplication();
    VhdFooterField creatorVersion = getCreatorVersion();
    VhdFooterField creatorHostOs = getCreatorHostOs();
    VhdFooterField originalSize = getOriginalSize();
    VhdFooterField currentSize = getCurrentSize();
    VhdFooterField diskGeometry = getDiskGeometry();
    VhdFooterField diskType = getDiskType();
    VhdFooterField uniqueId = getUniqueId();
    VhdFooterField savedState = getSavedState();
    VhdFooterField reserved = getReserved();
    VhdFooterField checksum = getChecksum();
    List<VhdFooterField> vhdFooterFieldList = List.of(
      cookie, features, fileFormatVersion, dataOffset,
      timeStamp, creatorApplication, creatorVersion, creatorHostOs,
      originalSize, currentSize, diskGeometry, diskType,
      checksum, uniqueId, savedState, reserved
    );
    calculateChecksum(checksum, vhdFooterFieldList);
    List<Byte> footerList = new ArrayList<>();
    for (VhdFooterField vhdFooterField : vhdFooterFieldList) {
      footerList.addAll(vhdFooterField.valueList());
    }
    return Convert.convert(byte[].class, footerList);
  }

  @NotNull
  protected static VhdFooterField getCookie() {
    return new VhdFooterField(
      COOKIE,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(COOKIE),
      string2ByteList("conectix")
    );
  }

  @NotNull
  protected static VhdFooterField getFeatures() {
    return new VhdFooterField(
      FEATURES,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(FEATURES),
      // Reserved
      List.of((byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02)
    );
  }

  @NotNull
  protected static VhdFooterField getFileFormatVersion() {
    return new VhdFooterField(
      FILE_FORMAT_VERSION,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(FILE_FORMAT_VERSION),
      // For the current specification, this field must be initialized to 0x00010000
      List.of((byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00)
    );
  }

  @NotNull
  protected static VhdFooterField getDataOffset() {
    return new VhdFooterField(
      DATA_OFFSET,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(DATA_OFFSET),
      // For fixed disks, this field should be set to 0xFFFFFFFFFFFFFFFF
      List.of(
        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
      )
    );
  }

  @NotNull
  protected static VhdFooterField getTimeStamp() {
    // the number of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
    long secondsSince2000 = (System.currentTimeMillis() -
      new GregorianCalendar(2000, Calendar.JANUARY, 1).getTimeInMillis()) / 1000;
    byte[] bytesOfSeconds = ByteUtil.intToBytes((int) secondsSince2000, ByteOrder.BIG_ENDIAN);
    return new VhdFooterField(
      TIME_STAMP,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(TIME_STAMP),
      List.of(bytesOfSeconds[0], bytesOfSeconds[1], bytesOfSeconds[2], bytesOfSeconds[3])
    );
  }

  @NotNull
  protected static VhdFooterField getCreatorApplication() {
    return new VhdFooterField(
      CREATOR_APPLICATION,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(CREATOR_APPLICATION),
      // Only 4 bytes available, asm2 is the prefix of asm2vhd4bochs
      string2ByteList("asm2")
    );
  }

  @NotNull
  protected static VhdFooterField getCreatorVersion() {
    return new VhdFooterField(
      CREATOR_VERSION,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(CREATOR_VERSION),
      // This field holds the major/minor version of the application that created the hard disk image.
      byteListWithZeros(4)
    );
  }

  @NotNull
  protected static VhdFooterField getCreatorHostOs() {
    return new VhdFooterField(
      CREATOR_HOST_OS,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(CREATOR_HOST_OS),
      string2ByteList("Wi2k")
    );
  }

  @NotNull
  protected static VhdFooterField getOriginalSize() {
    return new VhdFooterField(
      ORIGINAL_SIZE,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(ORIGINAL_SIZE),
      // This field stores the size of the hard disk in bytes,
      // from the perspective of the virtual machine, at creation time.
      // This field is for informational purposes. 0x0030000000000000
      fileSize2ByteListSized8(vhdFileSize)
    );
  }

  @NotNull
  protected static VhdFooterField getCurrentSize() {
    return new VhdFooterField(
      CURRENT_SIZE,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(CURRENT_SIZE),
      // This value is same as the original size when the hard disk is created.
      // This value can change depending on whether the hard disk is expanded.
      fileSize2ByteListSized8(vhdFileSize)
    );
  }

  @NotNull
  protected static VhdFooterField getDiskGeometry() {
    VhdFooterField diskGeometry = new VhdFooterField(
      DISK_GEOMETRY,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(DISK_GEOMETRY),
      new ArrayList<>()
    );
    // The algorithm is from here: https://download.microsoft.com/download/f/f/e/ffef50a5-07dd-4cf8-aaa3-442c0673a029/Virtual%20Hard%20Disk%20Format%20Spec_10_18_06.doc
    // Sorry for the magic numbers, I don't know their meanings.
    int totalSectors = vhdFileSize / 512;
    int sectorsPerTrack;
    int heads;
    int cylinderTimesHeads;
    int cylinders;

    int maxHeads = 16;
    sectorsPerTrack = 17;
    cylinderTimesHeads = totalSectors / sectorsPerTrack;

    int magic1024 = 1024;
    heads = (cylinderTimesHeads + 1023) / magic1024;

    int minimumHeads = 4;
    if (heads < minimumHeads) {
      heads = minimumHeads;
    }
    if (cylinderTimesHeads >= (heads * magic1024) || heads > maxHeads) {
      sectorsPerTrack = 31;
      heads = maxHeads;
      cylinderTimesHeads = totalSectors / sectorsPerTrack;
    }
    if (cylinderTimesHeads >= (heads * magic1024)) {
      sectorsPerTrack = 63;
      heads = maxHeads;
      cylinderTimesHeads = totalSectors / sectorsPerTrack;
    }
    cylinders = cylinderTimesHeads / heads;
    byte[] bytesOfCylinders = ByteUtil.intToBytes(cylinders, ByteOrder.BIG_ENDIAN);
    List<Byte> valueList = diskGeometry.valueList();
    valueList.add(bytesOfCylinders[2]);
    valueList.add(bytesOfCylinders[3]);
    byte[] bytesOfHeads = ByteUtil.intToBytes(heads, ByteOrder.BIG_ENDIAN);
    valueList.add(bytesOfHeads[3]);
    byte[] bytesOfSectorsPerTrack = ByteUtil.intToBytes(sectorsPerTrack, ByteOrder.BIG_ENDIAN);
    valueList.add(bytesOfSectorsPerTrack[3]);
    return diskGeometry;
  }

  @NotNull
  protected static VhdFooterField getDiskType() {
    return new VhdFooterField(
      DISK_TYPE,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(DISK_TYPE),
      // This field stores the type of the disk.
      // 0x00000002 means fixed disk
      List.of((byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02)
    );
  }

  @NotNull
  protected static VhdFooterField getUniqueId() {
    VhdFooterField uniqueId = new VhdFooterField(
      UNIQUE_ID,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(UNIQUE_ID),
      // Contains a 16 bytes big-endian GUID
      new ArrayList<>()
    );
    byte[] bytes1 = ByteUtil.longToBytes(RandomUtil.randomLong(), ByteOrder.BIG_ENDIAN);
    byte[] bytes2 = ByteUtil.longToBytes(RandomUtil.randomLong(), ByteOrder.BIG_ENDIAN);
    List<Byte> uniqueIdValueList = uniqueId.valueList();
    for (int i = 0; i < bytes1.length; i++) {
      uniqueIdValueList.add(bytes1[i]);
      uniqueIdValueList.add(bytes2[i]);
    }
    return uniqueId;
  }

  @NotNull
  protected static VhdFooterField getSavedState() {
    return new VhdFooterField(
      SAVED_STATE,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(SAVED_STATE),
      // This field holds a one-byte flag that describes whether the system is in saved state.
      // If the hard disk is in the saved state the value is set to 1.
      // Operations such as compaction and expansion cannot be performed on a hard disk in a saved state.
      byteListWithZeros(1)
    );
  }

  @NotNull
  protected static VhdFooterField getReserved() {
    return new VhdFooterField(
      RESERVED,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(RESERVED),
      // This field is reserved for future use. This field is set to zero.
      byteListWithZeros(427)
    );
  }

  @NotNull
  protected static VhdFooterField getChecksum() {
    return new VhdFooterField(
      CHECKSUM,
      ZERO_BASED_ORDER_TO_SIZE_MAP.get(CHECKSUM),
      // This field holds a 4-byte basic checksum of the hard disk footer.
      // It is just a one’s complement of the sum of all the bytes in the footer without the checksum field.
      new ArrayList<>()
    );
  }

  protected static void calculateChecksum(VhdFooterField checksum, List<VhdFooterField> vhdFooterFieldList) {
    int sum = 0;
    for (int i = 0; i < vhdFooterFieldList.size(); i++) {
      VhdFooterField vhdFooterField = vhdFooterFieldList.get(i);
      Assert.isTrue(i == vhdFooterField.zeroBasedOrder());
      if (i != VhdFooterFieldConstant.ZeroBasedOrder.CHECKSUM) {
        sum += vhdFooterField.valueList()
                             .stream()
                             .mapToInt(b -> b & 0xFF)
                             .sum();
      }
    }
    int onesComplement = ~sum;
    byte[] bytes = ByteUtil.intToBytes(onesComplement, ByteOrder.BIG_ENDIAN);
    List<Byte> valueList = checksum.valueList();
    for (byte b : bytes) {
      valueList.add(b);
    }
  }

}
